package cn.siriusbot.siriuspro.bot.plugin.factory;

import cn.siriusbot.siriuspro.bot.annotation.*;
import cn.siriusbot.siriuspro.bot.plugin.PlugInClient;
import cn.siriusbot.siriuspro.bot.plugin.PlugInFactory;
import cn.siriusbot.siriuspro.bot.plugin.QuickPlugInClient;
import cn.siriusbot.siriuspro.bot.plugin.pojo.QuickAppEntity;
import cn.siriusbot.siriuspro.config.Constant;
import cn.siriusbot.siriuspro.error.MsgException;
import cn.siriusbot.siriuspro.uitls.AppContextUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.util.ObjectUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Log4j2
public class JavaSiriusAppFactory implements SiriusAppFactory {

    /**
     * HttpClient单例
     */
    public static final OkHttpClient httpClient = new OkHttpClient.Builder()
            .callTimeout(Duration.ofSeconds(10))     // 设置超时时间为10秒
            .build();

    Map<String, Object> componentMap = new ConcurrentHashMap<>();


    /**
     * 扫描@SiriusComponent注解
     *
     * @param clazz 被扫描的类
     */
    @Override
    public void scanComponent(Class<?> clazz) {
        SiriusComponent component = clazz.getAnnotation(SiriusComponent.class);
        if (component == null) return;
        String name = component.name();
        if (ObjectUtils.isEmpty(name)) {
            name = clazz.getName();
        }
        try {
            Constructor<?> declaredConstructor = clazz.getDeclaredConstructor();
            Object o = declaredConstructor.newInstance();
            if (componentMap.containsKey(name)) {
                throw new MsgException(500, String.format("实例化对象%s失败，因为已经存在名称为%s的对象", clazz, name));
            }
            componentMap.put(name, o);

            // 构造bean对象
            Class<?> configClazz = o.getClass();
            Method[] methods = configClazz.getMethods();
            for (Method method : methods) {
                SiriusBean bean = method.getAnnotation(SiriusBean.class);
                if (bean == null) continue;
                String beanName = bean.name();
                if (ObjectUtils.isEmpty(beanName)) {
                    beanName = configClazz.getName();
                }
                try {
                    if (componentMap.containsKey(beanName)) {
                        throw new MsgException(500, String.format("实例化对象%s失败，因为已经存在名称为%s的对象", configClazz, beanName));
                    }
                    Object invoke = method.invoke(o);
                    componentMap.put(beanName, invoke);
                } catch (MsgException e) {
                    log.error(e.getMessage(), e);
                } catch (Throwable e) {
                    log.error(String.format("实例化Bean对象%s失败", configClazz), e);
                }
            }
        } catch (MsgException e) {
            log.error(e.getMessage(), e);
        } catch (Throwable e) {
            log.error(String.format("实例化对象%s失败", clazz), e);
        }
    }


    @Data
    private static class ProxyInfo {
        Object source;
        Map<Method, Method> methodMap = new ConcurrentHashMap<>();
    }

    private ProxyInfo buildProxyInfo(Class<?> clazz, Object source) {
        final ProxyInfo proxyInfo = new ProxyInfo();
        proxyInfo.source = source;
        final Method[] methods = clazz.getMethods();
        final Class<?> sourceClass = source.getClass();
        final Method[] sourceMethods = sourceClass.getMethods();
        for (Method method : methods) {
            final Optional<Method> first = Arrays.stream(sourceMethods).filter(s -> compareMethods(method, s)).findFirst();
            if (first.isEmpty()) {
                throw new MsgException(500, "提供的API接口与依赖应用的接口不一致!");
            }
            proxyInfo.methodMap.put(method, first.get());
        }
        return proxyInfo;
    }

    /**
     * 比较两个 Method 对象的返回类型、方法名和参数列表是否完全一致。
     *
     * @param method1 要比较的 Method 对象之一
     * @param method2 要比较的另一个 Method 对象
     * @return 两个 Method 对象的返回类型、方法名和参数列表是否完全一致
     */
    private boolean compareMethods(Method method1, Method method2) {
        // 比较方法名
        if (!method1.getName().equals(method2.getName())) {
            return false;
        }

        // 比较参数类型
        Class<?>[] parameterTypes1 = method1.getParameterTypes();
        Class<?>[] parameterTypes2 = method2.getParameterTypes();

        if (parameterTypes1.length != parameterTypes2.length) {
            return false;
        }

//        for (int i = 0; i < parameterTypes1.length; i++) {
//            if (!parameterTypes1[i].equals(parameterTypes2[i])) {
//                return false;
//            }
//        }

        return true;
    }


    private Object getProxy(Class<?> clazz, ProxyInfo proxyInfo) {
        return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, (proxy, method, args) -> {
            final Object source = proxyInfo.getSource();
            final Method sourceMethod = proxyInfo.methodMap.get(method);
            if (sourceMethod == null) {
                return method.invoke(source, args);
            }
            final String argsJson = JSONArray.toJSONString(args);
            final JSONArray parse = JSONArray.parse(argsJson);
            Object invoke;
            if (args != null && args.length > 0) {
                Object[] serialize = new Object[parse.size()];
                final Class<?>[] parameterTypes = sourceMethod.getParameterTypes();
                for (int i = 0; i < parse.size(); i++) {
                    final Object item = parse.get(i);
                    if (item instanceof JSONObject jsonObject) {
                        serialize[i] = jsonObject.to(parameterTypes[i]);
                    } else {
                        serialize[i] = item;
                    }
                }
                invoke = sourceMethod.invoke(source, serialize);
            } else {
                invoke = sourceMethod.invoke(source);
            }
            final String result = JSONObject.toJSONString(invoke);
            return JSONObject.parseObject(result, method.getReturnType());
        });
    }

    @SneakyThrows
    private QuickAppEntity getQuickAppName(String packageName) {
        Request request = new Request.Builder()
                .url(Constant.FAST_APP_URL + "/api/quick/package-app-name/" + packageName)
                .get()
                .build();
        Response response = httpClient.newCall(request).execute();
        if (response.code() == 200) {// 正常接收
            String string = Objects.requireNonNull(response.body()).string();
            return JSONObject.parseObject(string, QuickAppEntity.class);
        }
        return null;
    }

    /**
     * 初始化所有的组件
     *
     * @param loadDependence 是否自动加载依赖
     */
    @Override
    public void startComponent(boolean loadDependence) {
        // 注入字段
        componentMap.forEach((key, val) -> {
            Class<?> clazz = val.getClass();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                SiriusWired siriusWired = field.getAnnotation(SiriusWired.class);
                if (siriusWired == null) continue;
                String name = siriusWired.name();
                Object o;
                if (ObjectUtils.isEmpty(name)) {
                    // 根据字段注入
                    o = getComponent(field.getType());
                } else {
                    // 根据名称注入
                    o = getComponent(name);
                }
                field.setAccessible(true); // 允许访问私有字段
                try {
                    field.set(val, o);
                } catch (IllegalAccessException e) {
                    log.error(String.format("%s组件注入%s失败", key, field), e);
                }
            }

        });
        // 注入API
        final PlugInFactory plugInFactory = AppContextUtil.getBean(PlugInFactory.class);
        componentMap.forEach((key, val) -> {
            Class<?> clazz = val.getClass();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                SiriusWiredApi siriusWiredApi = field.getAnnotation(SiriusWiredApi.class);
                if (siriusWiredApi == null) continue;
                final String packageName = siriusWiredApi.packageName();
                PlugInClient client = plugInFactory.getPlugInClientByPackageName(packageName);
                if (client == null) {
                    if (loadDependence) {
                        // 自动加载依赖
                        try {
                            final QuickAppEntity quickAppName = getQuickAppName(siriusWiredApi.packageName());
                            if (quickAppName == null || quickAppName.getName() == null) throw new RuntimeException();
                            client = new QuickPlugInClient(quickAppName.getName(), true);
                            plugInFactory.add(client);
                        } catch (Throwable e) {
                            throw new MsgException(500, String.format("获取依赖(%s)[%s]应用失败，请手动启动!", siriusWiredApi.packageName(), siriusWiredApi.appName()));
                        }
                    } else {
                        throw new MsgException(502, String.format("该插件需依赖(%s)[%s]应用，请先启动", siriusWiredApi.packageName(), siriusWiredApi.appName()));
                    }
                }
                final Class<?> type = field.getType();
                if (!type.isInterface()) throw new MsgException(500, "外部API只能通过接口");

                if (client instanceof QuickPlugInClient quickPlugInClient) {
                    final SiriusAppFactory factory = quickPlugInClient.getFactory();
                    final List<Object> siriusAppApi = factory.getComponentByAnnotation(SiriusAppApi.class);
                    final Optional<Object> first = siriusAppApi.stream().filter(s -> s.getClass().getAnnotation(SiriusAppApi.class).name().equals(siriusWiredApi.apiName())).findFirst();
                    if (first.isEmpty()) {
                        throw new MsgException(500, String.format("找不到(%s)[%s]应用的API%s", siriusWiredApi.packageName(), siriusWiredApi.appName(), siriusWiredApi.apiName()));
                    }
                    final Object o = first.get();
                    final ProxyInfo proxyInfo = buildProxyInfo(type, o);
                    final Object proxy = getProxy(type, proxyInfo);
                    field.setAccessible(true); // 允许访问私有字段
                    try {
                        field.set(val, proxy);
                    } catch (IllegalAccessException e) {
                        log.error(String.format("%s组件注入%s失败", key, field), e);
                    }
                }
            }
        });

        // 调用所有组件的初始化方法
        componentMap.forEach((key, val) -> {
            Class<?> clazz = val.getClass();
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                SiriusComponentStart start = method.getAnnotation(SiriusComponentStart.class);
                if (start != null) {
                    try {
                        method.invoke(val);
                        return;
                    } catch (Throwable e) {
                        log.error(String.format("初始化组件%s时遇到问题", key), e);
                    }
                }
            }
        });
    }

    /**
     * 初始化所有的组件
     */
    @Override
    public void startComponent() {
        this.startComponent(false);
    }

    /**
     * 通过类型获取组件
     *
     * @param clazz 类型
     * @return 组件对象
     */
    @Override
    public <T> T getComponent(Class<T> clazz) {
        List<Object> objects = componentMap.values().stream().filter(s -> clazz.isAssignableFrom(s.getClass())).toList();
        if (objects.isEmpty()) {
            throw new MsgException(500, String.format("找不到类型为%s的组件", clazz));
        }
        if (objects.size() > 1) {
            throw new MsgException(500, String.format("类型为%s的组件具有两个实现类，无法指定类型注入，请选择名称注入", clazz));
        }
        return (T) objects.get(0);
    }

    /**
     * 通过注解类型获取组件
     *
     * @param clazz 类型
     * @return 组件对象
     */
    @Override
    public <T extends Annotation> List<Object> getComponentByAnnotation(Class<T> clazz) {
        return componentMap.values().stream().filter(s -> s.getClass().getAnnotation(clazz) != null).toList();
    }

    /**
     * 通过名字获取组件
     *
     * @param name 名字
     * @return 组件对象
     */
    @Override
    public Object getComponent(String name) {
        Object o = componentMap.get(name);
        if (o == null) throw new MsgException(500, String.format("找不到名称为%s的组件", name));
        return o;
    }

    /**
     * 通过注解获取方法列表
     *
     * @param annotation 注解对象
     * @return 方法对象列表
     */
    @Override
    public <T extends Annotation> List<SiriusMethod<T>> getSiriusMethod(Class<T> annotation) {
        List<SiriusMethod<T>> result = new ArrayList<>();
        componentMap.forEach((key, val) -> {
            Class<?> clazz = val.getClass();
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                T methodAnnotation = method.getAnnotation(annotation);
                if (methodAnnotation != null) {
                    SiriusMethod<T> siriusMethod = new SiriusMethod<T>()
                            .setAnnotation(methodAnnotation)
                            .setMethod(method)
                            .setSource(val);
                    result.add(siriusMethod);
                }
            }
        });
        return result;
    }

    /**
     * 添加Bean对象
     *
     * @param name 名称
     * @param o    对象
     */
    @Override
    public void addBean(String name, Object o) {
        if (componentMap.containsKey(name)) {
            throw new MsgException(500, String.format("写入Bean失败，因为已经存在名称为%s的对象", name));
        }
        componentMap.put(name, o);
    }
}
